package pt.compflow.job;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import pt.compflow.Context;
import pt.compflow.activity.ActivityControllerListener;
import pt.compflow.flowontology.model.Event;
import pt.compflow.flowontology.model.EventInterface;
import pt.compflow.flowontology.model.FLOWO;
import pt.compflow.flowontology.model.FlowOntologyModel;
import pt.compflow.flowontology.model.FlowOntologyModelFactory;
import pt.compflow.flowontology.model.Job;
import pt.compflow.flowontology.model.JobTemplate;
import pt.compflow.flowontology.model.Person;
import pt.compflow.flowontology.model.Workflow;
import pt.compflow.graph.Tree;
import pt.compflow.interfaces.IOInterface;
import pt.compflow.interfaces.event.EventSubscription;
import pt.compflow.storage.TripleStoreConnection;
import pt.compflow.tools.OntModelHelper;
import pt.ipp.isep.gecad.o2c.model.impl.DateContainer;
import pt.ipp.isep.gecad.o2c.model.impl.DateContainer.DateContainerType;

import com.hp.hpl.jena.ontology.OntClass;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.rdf.model.StmtIterator;
import com.hp.hpl.jena.util.iterator.ExtendedIterator;
import com.hp.hpl.jena.vocabulary.RDF;

public class JobControllerImpl implements JobController {

	private static final Logger LOG = LoggerFactory.getLogger(JobControllerImpl.class);
		
	protected Context context;
	
	// outside listeners waiting for info on the state of jobs
	protected List<ActivityControllerListener> listeners;
	
	// flag indicating if the shutdown() method has been invoked
	protected boolean shutdown = false;
	
	// in-memory model to save active instantiation events
	protected FlowOntologyModel instEventModel;
	
	public JobControllerImpl(Context context){
		this.context = context;
		instEventModel = FlowOntologyModelFactory.createDefaultModel();
		listeners = new ArrayList<ActivityControllerListener>();
	}
	
//	public void startPending() {
//		TripleStoreConnection store = context.getStoreConnection();
//		store.openStore();
//		
//		FlowOntologyModel engineModel = store.connectModel();
//		Iterator<Job> it = engineModel.listJobs();
//		
//		while(it.hasNext()) {
//			Job job = it.next();
//			context.getWorkflowController().startPending(job.getURI());
//		}
//		
//		store.closeStore();
//	}
	
	@Override
	public void start() {
		// the JobController needs to listen to WorkflowController events and InstantiationEvent events
		context.getEventInterfaceManager().addInterfaceListener(this);
		context.getWorkflowController().addListener(this);
	}
	
	@Override
	public void stop() {
		TripleStoreConnection store = context.getStoreConnection();
		store.openStore();
		
		FlowOntologyModel engineModel = store.connectModel();
			
		// unsubscribe EventInterfaces listening to InstantiationEvents
		Iterator<Event> eventIt = instEventModel.listEvents();
		while(eventIt.hasNext()) {
			Event ev = eventIt.next();
			try {
				
				Job job = ev.getPropertyResourceValue(FLOWO.isEventOfJob).as(Job.class);
				Person owner = engineModel.listStatements(null, FLOWO.owns, job).next().getSubject().as(Person.class);
				FlowOntologyModel jobModel = store.connectNamedModel(job.getURI());
							
				Resource jobTemplate = ev.getProperty(FLOWO.hasJobTemplate).getObject().asResource();
				Resource wfDef = ev.getProperty(FLOWO.hasWorkflowDefinition).getObject().asResource();	
				String ontoUri = wfDef.getProperty(FLOWO.ontology).getObject().asLiteral().getString();
				
				OntModel ontModel = context.getJobTemplateManager().getJobTemplateWorkflowDefinition(owner, jobTemplate.getLocalName(), wfDef.getLocalName(), ontoUri);
				OntModelHelper ontModelHelper = new OntModelHelper(ontModel, true);				
				
				Resource evClass = ontModelHelper.getDirectLeafType(ev, FLOWO.Event.getURI());
				
				StmtIterator evInterfaceIt = ev.listProperties(FLOWO.deliveredThrough);
				while(evInterfaceIt.hasNext()) {
					EventInterface evInterface = engineModel.getEventInterfaceWithURI(evInterfaceIt.next().getObject().asResource().getURI());
					Resource evInterfaceClass = ontModelHelper.getDirectLeafType(evInterface, FLOWO.EventInterface.getURI());
					IOInterface interfaze = context.getEventInterfaceManager().get(evInterface, evInterfaceClass, ontModel, context, store);
					((pt.compflow.interfaces.event.EventInterface)interfaze).unsubscribe(context, store, ontModelHelper, jobModel, engineModel, job.getURI(), evClass, null);
				}
				
			} catch (IOException e) {
				LOG.error("Unable to unsubscribe event " + ev.getURI(), e);
			}
		}
		
		// stop listening to WorkflowController events and InstantiationEvent events
		context.getWorkflowController().removeListener(this);
		context.getEventInterfaceManager().removeInterfaceListener(this);
			
		shutdown = true;
		
		store.closeStore();
	}
	
	public Job loadAndStartJob(String ownerUri, String jobTemplateId) {
		TripleStoreConnection store = context.getStoreConnection();
		store.openStore();
		
		try {
			FlowOntologyModel engineModel = store.connectModel();
			Person owner = engineModel.getPersonWithURI(ownerUri);
			
			FlowOntologyModel descModel = context.getJobTemplateManager().getJobTemplateDescriptor(owner, jobTemplateId, null);
			JobTemplate jobTemplate = descModel.getJobTemplate(jobTemplateId);
			
			Job job = engineModel.createJobWithURI(FLOWO.getURI() + context.getInstantiator().getUniqueJobId(engineModel));
			job.setLabel(jobTemplate.getLabel(null), null);
			job.setDescription(jobTemplate.getDescription(null), null);
			job.setHasCreationDate(new DateContainer(DateContainerType.DATETIME));
			//job.addProperty(FLOWO.hasJobTemplate, jobTemplate);
			
			owner.addOwnsJob(job);
			
			// load the input data
			FlowOntologyModel jobModel = store.connectNamedModel(job.getURI());
			
			//FIXME should this remain here?
			jobModel.add(job.listProperties());
			//--------------
			
			context.getJobTemplateManager().getJobTemplateInputData(owner, jobTemplateId, jobModel);
							
			// for each workflow in the job...
			StmtIterator stmtIt = jobTemplate.listProperties(FLOWO.hasWorkflowDefinition);
			while (stmtIt.hasNext()) {
				Statement stmt = stmtIt.next();
				Resource wfRes = stmt.getObject().asResource();
				String ontoUri = wfRes.getProperty(FLOWO.ontology).getObject().asLiteral().getString();

				// load the ontology model
				OntModel ontModel = context.getJobTemplateManager().getJobTemplateWorkflowDefinition(owner, jobTemplateId, wfRes.getLocalName(), ontoUri);
				OntModelHelper ontModelHelper = new OntModelHelper(ontModel);	
												
				Resource wfClass = ontModelHelper.getDirectLeafType(wfRes, FLOWO.Workflow.getURI());
							
				// register required instantiation events (these go to the on-memory model) if present
				// or just instantiate the workflow-definition and run otherwise
				Tree instEventTree = ontModelHelper.getApplicableEdgeDestinationsClosure(FLOWO.hasInstantiationEvent.getURI(), wfClass.getURI(), FLOWO.Workflow.getURI());
					
				if(instEventTree != null && !instEventTree.isEmpty()) {
					
					for(Tree instEvent : instEventTree) {
						Resource evClass = ontModel.getResource(instEvent.getNode());
						
						Resource ev = context.getInstantiator().getEventInstance(ontModelHelper, instEventModel, engineModel, evClass, null);
						ev.addProperty(FLOWO.hasJobTemplate, jobTemplate);
						ev.addProperty(FLOWO.hasWorkflowDefinition, wfRes);
						ev.addProperty(FLOWO.isEventOfJob, job);
						instEventModel.add(wfRes.listProperties());
//						instEventModel.add(wfRes, RDF.type, wfClass);
//						instEventModel.add(wfRes, FLOWO.ontology, wfRes.getProperty(FLOWO.ontology).getObject());
						
						Iterator<Statement> evInterfaceIt = ev.listProperties(FLOWO.deliveredThrough).toList().iterator();
						while(evInterfaceIt.hasNext()) {
							EventInterface evInterface = engineModel.getEventInterfaceWithURI(evInterfaceIt.next().getObject().asResource().getURI());
							Resource evInterfaceClass = ontModelHelper.getDirectLeafType(evInterface, FLOWO.EventInterface.getURI());
							IOInterface interfaze = context.getEventInterfaceManager().get(evInterface, evInterfaceClass, ontModel, context, store);
							String subId = ((pt.compflow.interfaces.event.EventInterface)interfaze).subscribe(context, store, ontModelHelper, jobModel, engineModel, job.getURI(), evClass, null);
							ev.addProperty(instEventModel.getProperty(EventSubscription.SUBSCRIPTION_PROP), instEventModel.createLiteral(subId));
						}
					}
					
				} else {
					// analyze the workflow class restrictions and instantiate workflow
					Workflow wf = context.getInstantiator().getWorkflowInstance(ontModelHelper, jobModel, engineModel, wfClass, null, job);
					StmtIterator it = wfRes.listProperties();
					while(it.hasNext()) {
						Statement s = it.next();
						if(s.getPredicate().getURI().compareTo(FLOWO.ontology.getURI()) != 0)
							wf.addProperty(s.getPredicate(), s.getObject());
					}
					context.getOntModelHelperManager().create(store, job.getURI(), wf.getLocalName(), ontModel);
					context.getWorkflowController().startWorkflow(job.getURI(), wf.getURI());
				}
			}
			
			store.closeStore();
			
			return job;
		} catch (IOException e) {
			store.closeStore();
			
			throw new RuntimeException("Could not load and run job!", e);
		}
	}
	
	public boolean addListener(ActivityControllerListener l) {
		if(l != null) {
			listeners.add(l);
			return true;
		}
		return false;
	}
	
	public boolean removeListener(ActivityControllerListener l) {
		if(l != null && listeners.contains(l)) {
			listeners.remove(l);
			return true;
		}
		return false;
	}
	
	public void removeAllListeners() {
		listeners.clear();
	}
	
	@Override
	public void onEvent(String subscriptionId, String jobUri, String eventClassUri, String eventUri, String interfaceUri) {
		TripleStoreConnection store = context.getStoreConnection();
		store.openStore();
		
		FlowOntologyModel jobModel = store.connectNamedModel(jobUri);
				
		if(eventUri == null || jobModel.getEventWithURI(eventUri) == null) {
			LOG.warn("Ignoring possible InstantiationEvent of type " + eventClassUri + " since no data (eventUri) is present!");
			store.closeStore();
			return;
		}
		
		FlowOntologyModel engineModel = store.connectModel();
		Event jobEvent = jobModel.getEventWithURI(eventUri);
		Job job = engineModel.getJobWithURI(jobUri);
		Person owner = engineModel.listStatements(null, FLOWO.owns, job).next().getSubject().as(Person.class);
		
		ExtendedIterator<Event> evIt = instEventModel.listEvents();
		while(evIt.hasNext()) {
			Event ev = evIt.next();
			if(ev.hasLiteral(instEventModel.getProperty(EventSubscription.SUBSCRIPTION_PROP), subscriptionId)) {
				
				try {
					LOG.debug("InstantiationEvent " + eventUri + " (" + eventClassUri + ") received! Starting new Workflow...");
									
					Resource jobTemplate = ev.getProperty(FLOWO.hasJobTemplate).getObject().asResource();
					Resource wfDef = ev.getProperty(FLOWO.hasWorkflowDefinition).getObject().asResource();
					Resource wfDefClass = wfDef.getPropertyResourceValue(RDF.type);
					String ontoUri = wfDef.getProperty(FLOWO.ontology).getObject().asLiteral().getString();
					
					OntModel ontModel = context.getJobTemplateManager().getJobTemplateWorkflowDefinition(owner, jobTemplate.getLocalName(), wfDef.getLocalName(), ontoUri);
					OntModelHelper ontModelHelper = new OntModelHelper(ontModel);	
					
					OntClass wfClass = ontModel.getOntClass(wfDefClass.getURI());
									
					Workflow wf = context.getInstantiator().getWorkflowInstance(ontModelHelper, jobModel, engineModel, wfClass, null, job);
					StmtIterator it = wfDef.listProperties();
					while(it.hasNext()) {
						Statement s = it.next();
						if(s.getPredicate().getURI().compareTo(FLOWO.ontology.getURI()) != 0)
							wf.addProperty(s.getPredicate(), s.getObject());
					}
					
					context.getOntModelHelperManager().create(store, job.getURI(), wf.getLocalName(), ontModel);
					
					// add instantiation event data
					wf.addHasInstantiationEventEvent(jobEvent);				
					
					context.getWorkflowController().startWorkflow(jobUri, wf.getURI());
					
				} catch (IOException e) {
					LOG.error("Could not instantiate workflow-definition from instantiation event!", e);
				}
			}
		}
		
		store.closeStore();
		
	}
	
	@Override
	public void onActivityFinished(String jobUri, String activityUri, List<String> nextActivities) {
		LOG.debug("[Job " + jobUri + "] Workflow " + activityUri + " has finished!");
		for(ActivityControllerListener l : listeners) 
			l.onActivityFinished(jobUri, activityUri, nextActivities);
	}

	@Override
	public void onActivityCancelled(String jobUri, String activityUri) {
		LOG.debug("[Job " + jobUri + "] Workflow " + activityUri + " has been cancelled!");	
		for(ActivityControllerListener l : listeners) 
			l.onActivityCancelled(jobUri, activityUri);
	}

	@Override
	public void onActivityStarted(String jobUri, String activityUri) {
		LOG.debug("[Job " + jobUri + "] Workflow " + activityUri + " has started!");
		for(ActivityControllerListener l : listeners) 
			l.onActivityStarted(jobUri, activityUri);
	}

	@Override
	public void onActivityError(String jobUri, String activityUri) {
		LOG.debug("[Job " + jobUri + "] Workflow " + activityUri + " has an error!");
		for(ActivityControllerListener l : listeners) 
			l.onActivityError(jobUri, activityUri);
	}
	
	@Override
	public void onActivityTimeout(String jobUri, String activityUri) {
		LOG.debug("[Job " + jobUri + "] Workflow " + activityUri + " has timed out!");
		for(ActivityControllerListener l : listeners) 
			l.onActivityTimeout(jobUri, activityUri);
	}
	
}
